﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
#if !SILVERLIGHT
using System.Drawing;
using System.Drawing.Imaging;
#endif
using System.Runtime.InteropServices;
using System.Threading;
using Equinoxe.EZNFC.Library;
using Equinoxe.EZNFC.Library.Tags;
using Equinoxe.NFC.EasyACR122;
using System.Linq;

namespace Equinoxe.EZNFC.ACR122.Library
{
    /// <summary>
    /// ACR122 device class
    /// </summary>
    /// <remarks></remarks>
    public class ACR122Device : INFCDevice
    {
        #region values

        /// <summary>
        /// Action to execute after successful asynchronous opening
        /// </summary>
        private Action _openCallback;

        /// <summary>
        /// Action to execute after unsuccessful asynchronous opening
        /// </summary>
        private Action<Exception> _openExceptionCallback;

        /// <summary>
        /// NFC device number.
        /// </summary>
        private int _phReader;

        /// <summary>
        /// Device bauds.
        /// </summary>
        private int _bauds;

        /// <summary>
        /// Device charset.
        /// </summary>
        private DeviceCharset _charset;

        /// <summary>
        /// Device port.
        /// </summary>
        private Ports _port;

        /// <summary>
        /// Led states.
        /// </summary>
        private readonly int[] _LEDStates = new[]
                                                {
                                                    acr122.ACR122_LED_STATE_OFF, acr122.ACR122_LED_STATE_OFF,
                                                    acr122.ACR122_LED_STATE_OFF, acr122.ACR122_LED_STATE_OFF
                                                };

        /// <summary>
        /// Command poll.
        /// </summary>
        private readonly byte[] _commandPoll = new byte[] { 0xD4, 0x4A, 0x01, 0x00 };

        /// <summary>
        /// Background worker polling for tags.
        /// </summary>
        private readonly BackgroundWorker _pollingWorker = new BackgroundWorker();

        /// <summary>
        /// Background worker polling for open.
        /// </summary>
        private readonly BackgroundWorker _openWorker = new BackgroundWorker();
        /// <summary>
        /// Tag observable collection.
        /// </summary>
        private readonly ObservableTagCollection _tagCollection;

        /// <summary>
        /// Device info.
        /// </summary>
        private static NFCDeviceInfo _deviceInfo = new NFCDeviceInfo
        {
            BaudsAvailable = new List<int> { 96000, 115200 },
            HasLCD = true,
            GraphicLCD = true,
            NumberOfLCDColumns = 16,
            NumberOfLCDRows = 2,
            NumberOfSAMSlots = 3,
            CharacterSetsAvailable = new List<DeviceCharset>
                                                                                        {
                                                                                            DeviceCharset.Ansi,
                                                                                            DeviceCharset.Unicode
                                                                                        },
            DeviceName = "ACR122",
            Manufacturer = "ACR",
            HasBuzzer = true,
            NumberOfLEDs = 4,
            LCDContrastLevels = 16
        };


        #endregion

        #region Properties

        /// <summary>
        /// Occurs when a tag is detected.
        /// </summary>
        /// <remarks></remarks>
        public event Events.NFCTagEventHandler TagDetected;

        /// <summary>
        /// Gets or sets the polling interval.
        /// </summary>
        /// <value>The polling interval.</value>
        /// <remarks></remarks>
        public int PollingInterval { get; set; }

        /// <summary>
        /// Observable tag collection.
        /// </summary>
        /// <remarks></remarks>
        public ObservableTagCollection TagCollection
        {
            get { return _tagCollection; }
        }

        /// <summary>
        /// Gets the device info (static property).
        /// </summary>
        /// <remarks></remarks>
        static public NFCDeviceInfo DeviceInfoStatic
        {
            get { return _deviceInfo; }
        }

        /// <summary>
        /// Gets the device info.
        /// </summary>
        /// <remarks></remarks>
        public NFCDeviceInfo DeviceInfo
        {
            get { return _deviceInfo; }
        }


        /// <summary>
        /// Gets the bauds.
        /// </summary>
        /// <remarks></remarks>
        public int Bauds { get { return _bauds; } }

        /// <summary>
        /// Gets the charset.
        /// </summary>
        /// <remarks></remarks>
        public DeviceCharset Charset { get { return _charset; } }

        /// <summary>
        /// Gets the port.
        /// </summary>
        /// <remarks></remarks>
        public Ports Port { get { return _port; } }

        #endregion

        #region Constructors/Destructors

        /// <summary>
        /// Prevents a default instance of the <see cref="ACR122Device"/> class from being created.
        /// </summary>
        /// <param name="charset">The charset.</param>
        /// <param name="port">The port.</param>
        /// <param name="bauds">The bauds.</param>
        /// <remarks></remarks>
        private ACR122Device(DeviceCharset charset, Ports port, int bauds)
        {
            _bauds = bauds;
            _port = port;
            _charset = charset;

            PollingInterval = 200;

            _tagCollection = new ObservableTagCollection();

            _pollingWorker.WorkerReportsProgress = true;
            _pollingWorker.WorkerSupportsCancellation = true;
            _pollingWorker.DoWork += Polling;
            _pollingWorker.ProgressChanged += (p, o) =>
            {
                if (TagDetected != null)
                {
                    TagCollection.NewTag(o.UserState as INFCTag);
                    TagDetected(this, new NFCTagEventArgs(o.UserState as INFCTag));
                }
            };

            _openWorker.DoWork += (s, e) => Open();
            _openWorker.RunWorkerCompleted += (s, e) =>
            {
                if ((e.Error != null) && (_openExceptionCallback != null))
                {
                    _openExceptionCallback(e.Error);
                }
                if (_openCallback != null)
                {
                    _openCallback();
                }
            };
        }

        /// <summary>
        /// Creates the device.
        /// </summary>
        /// <param name="charset">The charset.</param>
        /// <param name="port">The port.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static ACR122Device CreateDevice(DeviceCharset charset, Ports port)
        {
            return new ACR122Device(charset, port, _deviceInfo.BaudsAvailable.FirstOrDefault());
        }

        /// <summary>
        /// Creates the device.
        /// </summary>
        /// <param name="charset">The charset.</param>
        /// <param name="port">The port.</param>
        /// <param name="bauds">The bauds.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static ACR122Device CreateDevice(DeviceCharset charset, Ports port, int bauds)
        {
            return new ACR122Device(charset, port, bauds);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <remarks></remarks>
        public void Dispose()
        {
            ReleaseUnmanagedResources();
            if (_pollingWorker.IsBusy)
                _pollingWorker.CancelAsync();
        }


        /// <summary>
        /// Releases the unmanaged resources.
        /// </summary>
        /// <remarks></remarks>
        private void ReleaseUnmanagedResources()
        {
            Close();
        }

        #endregion

        #region Open/Close device

        /// <summary>
        /// Opens this device.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Open()
        {
            ErrorCodes result;

            acr122.ACR122_SetBaudRate(_phReader, Bauds);

            if (Charset == DeviceCharset.Ansi)
            {
                result = (ErrorCodes)acr122.ACR122_OpenA(GetPort(), ref _phReader);
            }
            else
            {
                result = (ErrorCodes)acr122.ACR122_OpenW(GetPort(), ref _phReader);
            }

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        public void OpenAsync(Action callback, Action<Exception> exceptionCallback)
        {
            _openCallback = callback;
            _openExceptionCallback = exceptionCallback;

            _openWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Closes this device.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public void Close()
        {
            if (_phReader == 0)
                return;

            var result = (ErrorCodes)acr122.ACR122_Close(_phReader);

            _phReader = result == ErrorCodes.NoError ? 0 : _phReader;

            StopPolling();

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        #endregion

        #region OtherOperations

        /// <summary>
        /// Gets the number of SAM slots.
        /// </summary>
        /// <param name="numberofslots">The number of slots.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void GetNumberOfSlots(out int numberofslots)
        {
            numberofslots = 0;

            var result = (ErrorCodes)acr122.ACR122_GetNumSlots(_phReader, ref numberofslots);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Sets the baud rate.
        /// </summary>
        /// <param name="bauds">The baud rate.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void SetBaudRate(int bauds)
        {
            var result = (ErrorCodes)acr122.ACR122_SetBaudRate(_phReader, bauds);

            if (result == ErrorCodes.NoError)
                _bauds = bauds;

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }


        /// <summary>
        /// Sets the timeouts.
        /// </summary>
        /// <param name="statustimeout">The statust imeout.</param>
        /// <param name="numstatusretry">The number of status retry.</param>
        /// <param name="responsetimeout">The response timeout.</param>
        /// <param name="numresponseretry">The number of response retry.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void SetTimeouts(UInt32 statustimeout, UInt32 numstatusretry, UInt32 responsetimeout, UInt32 numresponseretry)
        {

            var timeoutstruct = new acr122.ACR122_TIMEOUTS
            {
                numResponseRetries = numresponseretry,
                numStatusRetries = numstatusretry,
                responseTimeout = responsetimeout,
                statusTimeout = statustimeout
            };
            var result = (ErrorCodes)acr122.ACR122_SetTimeouts(_phReader, ref timeoutstruct);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Sets the timeouts.
        /// </summary>
        /// <param name="timeouts">The timeouts.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void SetTimeouts(ACR122Timeouts timeouts)
        {

            SetTimeouts(timeouts.StatusTimeout, timeouts.NumberOfStatusRetry, timeouts.ResponseTimeout,
                               timeouts.NumberOfResponseRetry);

        }

        /// <summary>
        /// Gets the timeouts.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public ACR122Timeouts GetTimeouts()
        {

            var timeoutstruct = new acr122.ACR122_TIMEOUTS();

            var result = (ErrorCodes)acr122.ACR122_GetTimeouts(_phReader, ref timeoutstruct);

            if (result == ErrorCodes.NoError)
                return new ACR122Timeouts { NumberOfResponseRetry = timeoutstruct.numResponseRetries, NumberOfStatusRetry = timeoutstruct.numStatusRetries, ResponseTimeout = timeoutstruct.responseTimeout, StatusTimeout = timeoutstruct.statusTimeout };

            throw new ACR122Exception { ErrorCode = result };
        }



        #endregion

        #region Firmware

        /// <summary>
        /// Gets the firmware.
        /// </summary>
        /// <param name="slot">The SAM slot.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public string GetFirmware(int slot = 0)
        {
            var firmwareinfo = string.Empty;

            var buffer = new byte[255];
            var buffersize = 255;

            var result = (ErrorCodes)acr122.ACR122_GetFirmwareVersion(_phReader, slot, buffer, ref buffersize);

            if (result == ErrorCodes.NoError)
            {
                for (var i = 0; i < buffersize; i++)
                {
                    firmwareinfo = firmwareinfo + Convert.ToChar(buffer[i]);
                }

                return firmwareinfo;
            }

            throw new ACR122Exception { ErrorCode = result };
        }

        #endregion

        #region LCD

        /// <summary>
        /// Displays the LCD message.
        /// </summary>
        /// <param name="rawmessage">The message.</param>
        /// <param name="row">The row.</param>
        /// <param name="column">The column.</param>
        /// <remarks></remarks>
        public void DisplayLCDMessage(string rawmessage, int row = 0, int column = 0)
        {
            rawmessage = rawmessage ?? string.Empty;

            var message = rawmessage.Length > _deviceInfo.NumberOfLCDColumns ? rawmessage.Substring(0, _deviceInfo.NumberOfLCDColumns - 1) : rawmessage;

            var result = (ErrorCodes)acr122.ACR122_DisplayLcdMessageA(_phReader, row, column, message);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        public void DisplayLCDImage(string imagepath)
        {
            var image = new Bitmap(imagepath);

            DrawLCDImage(image);

        }

        public void LCDBacklight(bool lcdon)
        {
            acr122.ACR122_EnableLcdBacklight(_phReader, lcdon);
        }

        public void LCDClear()
        {
            acr122.ACR122_ClearLcd(_phReader);
        }

        public void LCDScroll(LCDSCrollSpeed speed = LCDSCrollSpeed.Medium, LCDScrollDirection scrollDirection = LCDScrollDirection.LeftToRight)
        {
            acr122.ACR122_StartLcdScrolling(_phReader, 0, 0, 15, 31, (int)speed, (int)scrollDirection);
        }


        public void SetLEDS(IEnumerable<bool> states)
        {
            var i = 0;
            foreach (var state in states)
            {
                SetLED(i, state);
                i++;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is poling.
        /// </summary>
        /// <remarks></remarks>
        public bool IsPoling
        {
            get { return _pollingWorker.IsBusy; }
        }

        /// <summary>
        /// Displays a LCD message ex.
        /// </summary>
        /// <param name="rawmessage">The rawmessage.</param>
        /// <param name="boldmode">The boldmode.</param>
        /// <param name="tableindex">The tableindex.</param>
        /// <param name="row">The row.</param>
        /// <param name="column">The column.</param>
        /// <remarks></remarks>
        public void DisplayLCDMessageEx(string rawmessage, int boldmode, int tableindex, int row = 0, int column = 0)
        {
            var message = rawmessage.Length > _deviceInfo.NumberOfLCDColumns ? rawmessage.Substring(0, _deviceInfo.NumberOfLCDColumns - 1) : rawmessage;

            var result = (ErrorCodes)acr122.ACR122_DisplayLcdMessageExA(_phReader, row, column, tableindex, boldmode, message);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Displays a LCD message in chinese.
        /// </summary>
        /// <param name="rawmessage">The rawmessage.</param>
        /// <param name="boldmode">The boldmode.</param>
        /// <param name="row">The row.</param>
        /// <param name="column">The column.</param>
        /// <remarks></remarks>
        public void DisplayLCDMessageChinese(string rawmessage, int boldmode, int row = 0, int column = 0)
        {
            var message = rawmessage.Length > _deviceInfo.NumberOfLCDColumns ? rawmessage.Substring(0, _deviceInfo.NumberOfLCDColumns / 2 - 1) : rawmessage;

            var result = (ErrorCodes)acr122.ACR122_DisplayLcdMessageGBA(_phReader, row, column, boldmode, message);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Draws a LCD line.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="bufferlength">The bufferlength.</param>
        /// <param name="lineindex">The lineindex.</param>
        /// <remarks></remarks>
        public void DrawLCDLine(byte[] buffer, int bufferlength, int lineindex = 0)
        {
            var result = (ErrorCodes)acr122.ACR122_DrawLcd(_phReader, lineindex, buffer, bufferlength);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }
#if !SILVERLIGHT
        /// <summary>
        /// Draws a LCD image.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <remarks></remarks>
        public void DrawLCDImage(string filename)
        {
            var image = new Bitmap(filename);

            DrawLCDImage(image);
        }



        /// <summary>
        /// Draws a LCD image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <remarks></remarks>
        public void DrawLCDImage(Bitmap image)
        {
            //if (image.PixelFormat != PixelFormat.Format24bppRgb)
            //    return ErrorCodes.WrongImageFormat;

            var result = ErrorCodes.NoError;

            try
            {
                ClearLCD();

                var imageheight = image.Height;

                var imagewidth = image.Width;

                // get maximum width and height to draw
                var maxline = imageheight > _deviceInfo.NumberOfLCDRows * 8 * 2 ? _deviceInfo.NumberOfLCDRows * 8 * 2 : imageheight;
                var maxwidth = imagewidth > _deviceInfo.NumberOfLCDColumns * 8 ? _deviceInfo.NumberOfLCDRows * 8 : imagewidth;

                var rect = new Rectangle(0, 0, imagewidth, maxline);
                var croppedbitmap = image.Clone(rect, PixelFormat.Format24bppRgb);


                var dummy = croppedbitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

                var ptr = dummy.Scan0;

                var bytes = croppedbitmap.Width * croppedbitmap.Height * 3;
                var buffer = new byte[bytes];

                // Copy the RGB values into the array.
                Marshal.Copy(ptr, buffer, 0, bytes);

                croppedbitmap.Dispose();
                image.Dispose();


                // draw line by line
                for (var i = 0; i < maxline; i++)
                {
                    var linebuffer = new byte[16];

                    var position = imagewidth * 3 * i;

                    for (var j = 0; j < _deviceInfo.NumberOfLCDColumns; j++)
                    {
                        if (j > maxwidth)
                        {
                            linebuffer[j] = 0;
                            continue;
                        }

                        byte currentbyte = 0;

                        for (var k = 7; k >= 0; k--)
                        {
                            var grayscale = (byte)(buffer[position] * 0.33 + buffer[position + 1] * 0.33 + buffer[position + 2] * 0.33);

                            currentbyte += grayscale >= 128 ? (byte)Math.Pow(2, k) : (byte)0;

                            position++;
                        }

                        linebuffer[j] = currentbyte;
                    }

                    result = (ErrorCodes)acr122.ACR122_DrawLcd(_phReader, i, linebuffer, linebuffer.Length);
                }
            }
            catch (Exception e)
            {
                throw;
            }

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }
#endif
        /// <summary>
        /// Starts the LCD scrolling.
        /// </summary>
        /// <param name="speed">The speed.</param>
        /// <param name="scrolldirection">The scrolldirection.</param>
        /// <remarks></remarks>
        public void StartLCDScrolling(int speed, LCDScrollDirection scrolldirection)
        {
            var result = (ErrorCodes)acr122.ACR122_StartLcdScrolling(_phReader, 0, 0, 15, 31, speed, (int)scrolldirection);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Stops the LCD scrolling.
        /// </summary>
        /// <remarks></remarks>
        public void LCDStopScroll()
        {
            var result = (ErrorCodes)acr122.ACR122_StopLcdScrolling(_phReader);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Pauses the LCD scrolling.
        /// </summary>
        /// <remarks></remarks>
        public void LCDPauseScroll()
        {
            var result = (ErrorCodes)acr122.ACR122_PauseLcdScrolling(_phReader);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Sets the LCD back light.
        /// </summary>
        /// <param name="on">if set to <c>true</c> [on].</param>
        /// <remarks></remarks>
        public void SetLCDBackLight(bool on = true)
        {
            var result = (ErrorCodes)acr122.ACR122_EnableLcdBacklight(_phReader, on);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Enables the LCD back light.
        /// </summary>
        /// <remarks></remarks>
        public void EnableLCDBackLight()
        {
            SetLCDBackLight();
        }

        /// <summary>
        /// Disables the LCD back light.
        /// </summary>
        /// <remarks></remarks>
        public void DisableLCDBackLight()
        {
            SetLCDBackLight(false);
        }

        /// <summary>
        /// Sets the LCD back light contrast.
        /// </summary>
        /// <param name="contrast">The contrast level</param>
        /// <remarks></remarks>
        public void LCDContrast(int contrast)
        {
            var result = (ErrorCodes)acr122.ACR122_SetLcdContrast(_phReader, contrast > 15 ? 15 : contrast < 0 ? 0 : contrast);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Clears the LCD.
        /// </summary>
        /// <remarks></remarks>
        public void ClearLCD()
        {
            var result = (ErrorCodes)acr122.ACR122_ClearLcd(_phReader);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        #endregion

        #region LED

        /// <summary>
        /// Enables a LED.
        /// </summary>
        /// <remarks></remarks>
        public void EnableLED()
        {
            var result = (ErrorCodes)acr122.ACR122_EnableLed(_phReader, true);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Disables a LED.
        /// </summary>
        /// <remarks></remarks>
        public void DisableLED()
        {
            var result = (ErrorCodes)acr122.ACR122_EnableLed(_phReader, false);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Sets LED.
        /// </summary>
        /// <param name="LEDNumnber">The LED numnber.</param>
        /// <param name="IsON">if set to <c>true</c> [is ON].</param>
        /// <remarks></remarks>
        public void SetLED(int LEDNumnber, bool IsON)
        {
            EnableLED();

            _LEDStates[LEDNumnber] = IsON ? acr122.ACR122_LED_STATE_ON : acr122.ACR122_LED_STATE_OFF;

            var result = (ErrorCodes)acr122.ACR122_SetLedStates(_phReader, _LEDStates, 4);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        /// <summary>
        /// Sets LED and beep.
        /// </summary>
        /// <param name="Blink">if set to <c>true</c> [blink].</param>
        /// <param name="InitialStateON">if set to <c>true</c> [initial state ON].</param>
        /// <param name="FinalStateON">if set to <c>true</c> [final state ON].</param>
        /// <param name="Time1">The time1.</param>
        /// <param name="Time2">The time2.</param>
        /// <param name="NumberOfTime">The number of time.</param>
        /// <param name="Buzzermode">The buzzermode.</param>
        /// <remarks></remarks>
        public void SetLEDAndBeep(bool Blink, bool InitialStateON, bool FinalStateON, int Time1, int Time2, int NumberOfTime, BuzzerMode Buzzermode)
        {
            EnableLED();
            var LEDStruct = new acr122.ACR122_LED_CONTROL[2]{ new acr122.ACR122_LED_CONTROL 
                                {
                                    blinkEnabled = Blink,
                                    initialBlinkingState = InitialStateON ? acr122.ACR122_LED_STATE_ON : acr122.ACR122_LED_STATE_OFF,
                                    finalState = FinalStateON ? acr122.ACR122_LED_STATE_ON : acr122.ACR122_LED_STATE_OFF,
                                    updateEnabled = true
                                },new acr122.ACR122_LED_CONTROL 
                                {
                                    blinkEnabled = Blink,
                                    initialBlinkingState = InitialStateON ? acr122.ACR122_LED_STATE_ON : acr122.ACR122_LED_STATE_OFF,
                                    finalState = FinalStateON ? acr122.ACR122_LED_STATE_ON : acr122.ACR122_LED_STATE_OFF,
                                    updateEnabled = true
                                }};

            var result = (ErrorCodes)acr122.ACR122_SetLedStatesWithBeep(_phReader, ref LEDStruct, 2, Time1, Time2, NumberOfTime, (int)Buzzermode);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        #endregion

        #region Buzzer

        /// <summary>
        /// Beeps the specified sound duration.
        /// </summary>
        /// <param name="ontime">Duration of the sound.</param>
        /// <param name="offtime">Duration of the silence.</param>
        /// <param name="numberoftimes">The number of times to repeat.</param>
        /// <remarks></remarks>
        public void Buzzer(int ontime, int offtime, int numberoftimes)
        {
            var result = (ErrorCodes)acr122.ACR122_Beep(_phReader, ontime, offtime, numberoftimes);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        #endregion

        #region Polling

        /// <summary>
        /// Starts the polling.
        /// </summary>
        /// <remarks></remarks>
        public void StartPolling()
        {
            if (!_pollingWorker.IsBusy)
                _pollingWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Stops the polling.
        /// </summary>
        /// <remarks></remarks>
        public void StopPolling()
        {
            if (_pollingWorker.IsBusy)
                _pollingWorker.CancelAsync();
        }

        /// <summary>
        /// Poll for tag.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
        /// <remarks></remarks>
        private void Polling(object sender, DoWorkEventArgs e)
        {
            var bw = sender as BackgroundWorker;
            uint latesttagid = 0;

            do
            {
                var tag = Poll();

                if ((tag != null) && (tag.TagID != latesttagid))
                {
                    bw.ReportProgress(0, tag);

                    latesttagid = tag.TagID;
                }

                Thread.Sleep(PollingInterval);

            } while (!bw.CancellationPending);
        }

        #endregion

        #region Others Helpers

        /// <summary>
        /// Gets the communication port.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public string GetPort()
        {
            return string.Format(@"\\.\COM{0}", (int)Port + 1);
        }

        #endregion

        #region Card Operations

        /// <summary>
        /// Polls the device for tag.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public INFCTag Poll()
        {

            var receivebuffer = new byte[255];
            var receivedbufferlength = 255;

            try
            {
                SendMessage(_commandPoll, 4, receivebuffer, ref receivedbufferlength);
            }
            catch (ACR122Exception e)
            {
                if (e.ErrorCode == ErrorCodes.NoTag)
                    return null;
                throw;
            }
            catch (Exception e)
            {
                throw;
            }

            if (receivebuffer[2] == 0)
                return null;

            switch (receivebuffer[6])
            {
                //case 00:
                //    return TagType.MifareUltralight;
                case 08:
                    return new Mifare1K(this, receivebuffer);
                //case 09:
                //    return TagType.MifareMini;
                //case 18:
                //    return TagType.Mifare4K;
                //case 20:
                //    return TagType.MifareDesfire;
                //case 28:
                //    return TagType.JCOP30;
                //case 98:
                //    return TagType.GemplusMPCOS;
            }

            return null;
        }

        /// <summary>
        /// Sends a message.
        /// </summary>
        /// <param name="sendbuffer">The sendbuffer.</param>
        /// <param name="sendbufferlength">The sendbufferlength.</param>
        /// <param name="receivebuffer">The receivebuffer.</param>
        /// <param name="receivebufferlength">The receivebufferlength.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public void SendMessage(byte[] sendbuffer, int sendbufferlength, byte[] receivebuffer, ref int receivebufferlength)
        {
            var result = (ErrorCodes)acr122.ACR122_DirectTransmit(_phReader, sendbuffer, sendbufferlength, receivebuffer, ref receivebufferlength);

            if (result != ErrorCodes.NoError)
            {
                throw new ACR122Exception { ErrorCode = result };
            }
        }

        #endregion

    }
}
